package cmd

import (
	"crypto/md5"
	"encoding/base64"
	"fmt"

	"gitee.com/qianyucc/secure-tool/util"

	"github.com/spf13/cobra"
)

const (
	// HASH_BYTES_LEN 哈希之后的字节长度
	HASH_BYTES_LEN = 9
	// SPECIAL_CHARS 特殊字符，添加在进行哈希之后的字符串中
	SPECIAL_CHARS = "5Rr*"
)

// 目的是自定义一个散列算法，使用数字、字母、特殊字符进行编码。可用于密码的设置。
// raincodeCmd 计算RainCode的核心方法
// 1、字符串加字符串长度加字符串Hash
// 2、计算MD5值
// 3、将MD5压缩成长度为16的字节数组
// 4、将字节数组错位对折、异或，变成8位字节数组（长度为9）
// 5、使用算法打乱 Base64Table （Hash取模、二叉树遍历）
// 6、使用打乱之后的 Base64Table 编码生成的字节数组
// 7、最后生成长度为12的字符串编码
// 8、添加特殊字符，生成长度为16的字符串编码
var raincodeCmd = &cobra.Command{
	Use:   "raincode",
	Short: "A new encode method for string",
	Long:  `A new encode method for string`,
	Run: func(cmd *cobra.Command, args []string) {
		var text string
		if text = util.ReadTextFromClipboard(); len(args) == 0 && text == "" {
			fmt.Println("invalid parameter numbers")
			return
		}
		if len(args) > 0 {
			text = args[0]
		}
		util.PrintAndCopyTextToClipboardIgnoreError(ComputeRainCode(text))
	},
}

// ComputeRainCode 计算 RainCode
func ComputeRainCode(text string) (code string) {
	var hash byte
	for _, b := range []byte(text) {
		hash = b ^ hash
	}
	text = fmt.Sprintf("%s%d%d", text, len(text), hash)
	// 计算MD5
	sum := md5.Sum([]byte(text))
	// 只需要前9位，把后面7位和前面进行异或运算
	for i := 0; i < HASH_BYTES_LEN-2; i++ {
		sum[i] = sum[i] ^ sum[i+HASH_BYTES_LEN]
	}
	// 替换最后两个字符
	base64Table := "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789._"
	// 打乱HashTable
	newTable := string(util.ResetBytesByBinTree([]byte(base64Table)))
	// Base64 编码
	enc := base64.NewEncoding(newTable).WithPadding(base64.NoPadding)
	r := enc.EncodeToString(sum[:HASH_BYTES_LEN])
	return InsertSpecialChars(r)
}

// InsertSpecialChars 插入特殊字符,根据hash计算插入位置。
// 插入之后便可以形成16位的包含特殊字符、字母大小写、数字的字符串
func InsertSpecialChars(text string) string {
	if len(text) == 0 {
		return ""
	}
	raw := []byte(text)
	chars := []byte(SPECIAL_CHARS)
	for _, ch := range chars {
		var hash byte
		for _, it := range raw {
			hash = hash ^ it
		}
		index := (int)(hash^ch) % len(raw)
		newRaw := make([]byte, len(raw)+1)
		copy(newRaw, raw[:index])
		copy(newRaw[index+1:], raw[index:])
		newRaw[index] = ch
		raw = newRaw
	}
	return string(raw)
}

func init() {
	rootCmd.AddCommand(raincodeCmd)
}
